using Game.Networking;
using Game.Networking.Server;
using UnityEngine;
using Voxels;

namespace Game {
	public sealed partial class SingleChunkProcessor : WorldEntity<ChunkState>, IDamageReceiver {
		public ChunkRenderer Renderer;

		Chunk _chunk;
		
		public override EntityType EntityType => EntityType.BlockGrid;
		public Vector3 CenterPos => transform.position + new Vector3(Chunk.CHUNK_SIZE_X, Chunk.CHUNK_SIZE_Y, Chunk.CHUNK_SIZE_Z) * 0.5f;

		public void Setup(Chunk chunk) {
			_chunk= chunk;
			Renderer.Setup(chunk);
		}

		private void Update() {
			if ( _chunk?.Dirty ?? false ) {
				_chunk.UpdateChunk();
			}
		}

		private void LateUpdate() {
			if ( _chunk?.NeedRebuildGeometry ?? false ) {
				_chunk.UpdateGeometry();
			}
		}

		public Vector3Int GetBlockPosFromWorldPos(Vector3 pos) {
			return _chunk?.GetBlockPosFromWorldPos(pos) ?? Vector3Int.zero;
		}

		public void RemoveBlock(Byte3 pos) {
			if ( pos.X >= Chunk.CHUNK_SIZE_X || pos.X >= Chunk.CHUNK_SIZE_Y || pos.X >= Chunk.CHUNK_SIZE_Z ) {
				return;
			}
			S_CallRPC_RemoveBlockInternal(pos, true);
		}

		public void RemoveBlock(Vector3Int pos) {
			RemoveBlock(new Byte3((byte)pos.x, (byte)pos.y, (byte)pos.z));
		}

		public void PutBlock(Byte3 pos, BlockData block) {
			if ( pos.X >= Chunk.CHUNK_SIZE_X || pos.X >= Chunk.CHUNK_SIZE_Y || pos.X >= Chunk.CHUNK_SIZE_Z ) {
				return;
			}
			S_CallRPC_SetBlock(pos, block, true);
		}

		[ClientRPC(16)]
		void SetBlock(Byte3 pos, BlockData block) {
			_chunk.PutBlock(pos.X, pos.Y, pos.Z, block);
			_summary = null;
		}

		[ClientRPC(17)]
		void RemoveBlockInternal(Byte3 pos) {
			_chunk.RemoveBlock(pos.X, pos.Y, pos.Z);
			_summary = null;
		}

		public void ApplyDamage(DamageDescription damage) {
			if ( !IsServer) {
				return;
			}
			var point = damage.HitPoint - damage.HitNormal * 0.03f;
			var pos = _chunk.GetBlockPosFromWorldPos(point);
			var block = _chunk.GetBlock(pos.x, pos.y, pos.z);
			if ( block.Type == BlockType.Air) {
				return;
			}
			RemoveBlock(pos);
			SpawnBlock(block, point, damage.HitNormal);
			//TODO: add mined block to stat?
		}

		void SpawnBlock(BlockData block, Vector3 hitPoint, Vector3 hitNormal) {
			if ( !IsServer ) { return; }
			var entity = ServerEntityController.Instance.CreateNewEntity(EntityType.CargoContainer, hitPoint + hitNormal * 0.2f, Random.rotation.eulerAngles, e => {
				var cast = (CargoContainer)e;
				cast.Setup(new System.Collections.Generic.List<BaseInventoryItem>(1) { new BlockItem() { Count = 1, ItemType = InventoryItemType.Block, BlockData = block } });
			});
			var rb = entity.GetComponent<Rigidbody>();
			rb.AddForce(hitNormal * 0.5f, ForceMode.Impulse);
			rb.AddTorque(Random.insideUnitSphere * 0.2f);
		}

		public override void ApplyFullState(EntityState state) {
			base.ApplyFullState(state);
			var cast = (ChunkState)state;
			if ( cast == null ) {
				return;
			}
			Setup(new Chunk(cast.Data));
		}

		public TeamType GetTeam() {
			return TeamType.Neutral;
		}

		protected override ChunkState SaveInternal() {
			var state = base.SaveInternal();
			state.Data = _chunk?.GetData();
			return state;
		}

		protected override string CreateSummary() {
			return $"type:asteroid,blocks:{_chunk?.BlockCount ?? 0}";
		}
	}
}
